Análise da evolução da COVID-19 no Brasil¶

Geração Tech Unimed-BH - Ciência de Dados¶

DIO - Digital Innovation One¶

In [1]:
#Import de bibliotecas
import pandas as pd
import numpy as np
from datetime import datetime
import plotly.express as px
import plotly.graph_objects as go
import re
from statsmodels.tsa.seasonal import seasonal_decompose
import matplotlib.pyplot as plt

Aqui vou importar os dados direto do Github e começar a tratar as colunas. Primeiro vou tratar as colunas do tipo data e depois exibir o dataset.

In [2]:
url = 'https://raw.githubusercontent.com/jcbdoliveira/Bootcamp-Unimed-BH-Evolucao-COVID-19/main/covid_19_data.csv'

df = pd.read_csv(url, parse_dates=['ObservationDate', 'Last Update'])
df
Out[2]:
SNo ObservationDate Province/State Country/Region Last Update Confirmed Deaths Recovered
0 1 2020-01-22 Anhui Mainland China 2020-01-22 17:00:00 1.0 0.0 0.0
1 2 2020-01-22 Beijing Mainland China 2020-01-22 17:00:00 14.0 0.0 0.0
2 3 2020-01-22 Chongqing Mainland China 2020-01-22 17:00:00 6.0 0.0 0.0
3 4 2020-01-22 Fujian Mainland China 2020-01-22 17:00:00 1.0 0.0 0.0
4 5 2020-01-22 Gansu Mainland China 2020-01-22 17:00:00 0.0 0.0 0.0
... ... ... ... ... ... ... ... ...
26708 26709 2020-05-19 Wyoming US 2020-05-20 02:32:19 776.0 10.0 0.0
26709 26710 2020-05-19 Xinjiang Mainland China 2020-05-20 02:32:19 76.0 3.0 73.0
26710 26711 2020-05-19 Yukon Canada 2020-05-20 02:32:19 11.0 0.0 11.0
26711 26712 2020-05-19 Yunnan Mainland China 2020-05-20 02:32:19 185.0 2.0 183.0
26712 26713 2020-05-19 Zhejiang Mainland China 2020-05-20 02:32:19 1268.0 1.0 1267.0

26713 rows × 8 columns

Conferindo para ver se os campos 'ObservationDate', 'Last Update' estão como datetime mesmo.

In [3]:
df.dtypes
Out[3]:
SNo                         int64
ObservationDate    datetime64[ns]
Province/State             object
Country/Region             object
Last Update        datetime64[ns]
Confirmed                 float64
Deaths                    float64
Recovered                 float64
dtype: object

Agora vou aplicar boas praticas de exploração de dados em Python.

  1. Subistituir / por espaço em branco com uma expressão Regex.
  2. Campos devem ser todos em letra minúscula.
In [4]:
def corrigir_colunas(col_name):
    return re.sub(r"[/| ]", "", col_name).lower()

df.columns = [corrigir_colunas(col) for col in df.columns]

df.head()
Out[4]:
sno observationdate provincestate countryregion lastupdate confirmed deaths recovered
0 1 2020-01-22 Anhui Mainland China 2020-01-22 17:00:00 1.0 0.0 0.0
1 2 2020-01-22 Beijing Mainland China 2020-01-22 17:00:00 14.0 0.0 0.0
2 3 2020-01-22 Chongqing Mainland China 2020-01-22 17:00:00 6.0 0.0 0.0
3 4 2020-01-22 Fujian Mainland China 2020-01-22 17:00:00 1.0 0.0 0.0
4 5 2020-01-22 Gansu Mainland China 2020-01-22 17:00:00 0.0 0.0 0.0

Até aqui estava apenas carregando e tratando os dados. As análises vão iniciar agora.

Primeiro vou filtrar os dados do Brasil e apresentar uma amostra.

In [5]:
df_Brasil = df.loc[(df.countryregion == 'Brazil') & (df.confirmed > 0)]
df_Brasil.head()
Out[5]:
sno observationdate provincestate countryregion lastupdate confirmed deaths recovered
2455 2456 2020-02-26 NaN Brazil 2020-02-26 23:53:02 1.0 0.0 0.0
2559 2560 2020-02-27 NaN Brazil 2020-02-26 23:53:02 1.0 0.0 0.0
2668 2669 2020-02-28 NaN Brazil 2020-02-26 23:53:02 1.0 0.0 0.0
2776 2777 2020-02-29 NaN Brazil 2020-02-29 21:03:05 2.0 0.0 0.0
2903 2904 2020-03-01 NaN Brazil 2020-02-29 21:03:05 2.0 0.0 0.0

Vamos visualizar os dados em gráfico

  1. Casos confirmados
  2. Mortes
  3. Recuperados
  4. Novos casos por dia
In [6]:
px.line(df_Brasil, 'observationdate', 'confirmed', 
        labels={'observationdate':'Data', 'confirmed':'Número de casos confirmados'},
       title='Casos confirmados')
In [7]:
fig = go.Figure()

fig.add_trace(
    go.Scatter(x=df_Brasil.observationdate, y=df_Brasil.deaths, name='Mortes', mode='lines+markers',
              line=dict(color='red'))
)

fig.update_layout(title='Mortes por COVID-19 no Brasil',
                   xaxis_title='Data',
                   yaxis_title='Número de mortes')
fig.show()
In [8]:
fig = go.Figure()

fig.add_trace(
    go.Scatter(x=df_Brasil.observationdate, y=df_Brasil.recovered, name='Mortes', mode='lines+markers',
              line=dict(color='orange'))
)

fig.update_layout(title='Recuperados',
                   xaxis_title='Data',
                   yaxis_title='Casos recuperados')
fig.show()

Para obter o número de novo casos vou criar uma nova coluna para analisar os casos de hoje menos os de ontem, desta forma podemos avaliar a evolução da doença.

In [ ]:
df_Brasil['casosdia'] = list(map(
    lambda x: 0 if (x==0) else df_Brasil['confirmed'].iloc[x] - df_Brasil['confirmed'].iloc[x-1],
    np.arange(df_Brasil.shape[0])
))
In [11]:
fig = go.Figure()

fig.add_trace(
    go.Scatter(x=df_Brasil.observationdate, y=df_Brasil.casosdia, name='Novos casos', mode='lines+markers',
              line=dict(color='black'))
)

fig.update_layout(title='Novos casos por dia',
                   xaxis_title='Data',
                   yaxis_title='Novos casos')
fig.show()

Dados estatísticos¶

Taxa de crescimento¶

Cálculo da taxa de crescimento do COVID desde o primeiro caso.

taxa_crescimento = (valor_presente/valor_passado) (1/n) - 1**

In [12]:
def taxa_crescimento(data, variable, data_inicial=None, data_final=None):
    # Se data_inicial igual a None, define como a primeira data disponível no dataset
    if data_inicial == None:
        data_inicial = data.observationdate.loc[data[variable] > 0].min()
    else:
        data_inicial = pd.to_datetime(data_inicial)
    
    # Se data_final igual a None, define como a ultima data disponível no dataset
    if data_final == None:
        data_final = data.observationdate.iloc[-1]
    else:
        data_final = pd.to_datetime(data_final)
    
    # Define os valores de presente e passado
    valor_passado = data.loc[data.observationdate == data_inicial, variable].values[0]
    valor_presente = data.loc[data.observationdate == data_final, variable].values[0]
    
    # Define o número de pontos no tempo q vamos avaliar
    n = (data_final - data_inicial).days
    _data_ini_taxa = data_inicial
    _data_fin_taxa = data_final 
 
    # Calcula a taxa
    taxa = (valor_presente/valor_passado)**(1/n) - 1
    return taxa * 100

crescimento_medio = taxa_crescimento(df_Brasil, 'confirmed')
print(f"O crescimento médio do COVID-19 no Brasil no período avaliado foi de {crescimento_medio.round(2)}%.")
O crescimento médio do COVID-19 no Brasil no período avaliado foi de 16.27%.

Agora, vamos observar o comportamento da taxa de crescimento no tempo. Para isso, vamos definir uma função para calcular a taxa de crescimento diária e apresentar um gráfico de linha.

In [13]:
def taxa_crescimento_diaria(data, variable, data_inicio=None):
    if data_inicio == None:
        data_inicio = data.observationdate.loc[data[variable] > 0].min()
    else:
        data_inicio = pd.to_datetime(data_inicio)
        
    data_fim = data.observationdate.max()
    n = (data_fim - data_inicio).days
    taxas = list(map(
        lambda x: (data[variable].iloc[x] - data[variable].iloc[x-1]) / data[variable].iloc[x-1],
        range(1,n+1)
    ))
    return np.array(taxas)*100

tx_dia = taxa_crescimento_diaria(df_Brasil, 'confirmed')

tx_dia
Out[13]:
array([  0.        ,   0.        , 100.        ,   0.        ,
         0.        ,   0.        , 100.        ,   0.        ,
       225.        ,   0.        ,  53.84615385,  25.        ,
        24.        ,  22.58064516,  36.84210526, 190.38461538,
         0.        ,   7.28476821,  23.45679012,  60.5       ,
        15.88785047,  66.93548387,  27.69726248,  28.75157629,
        51.4201763 ,  24.45019405,  16.78794179,  13.66266133,
        16.87548943,  14.47236181,  14.25226807,   9.01639344,
         7.58928571,  24.8525879 ,  19.57320273,  17.67115272,
        12.58080557,  14.39929329,   7.43243243,   9.26325247,
        15.40169394,  15.22017956,  11.88620903,   8.54521335,
         5.54537122,   7.06807546,   5.57858688,   7.81903542,
        12.10513815,   7.4329096 ,  10.70501233,   8.83557983,
         5.44492335,   5.4043566 ,   5.73350023,   6.21648599,
         9.35157462,   8.00823407,   9.77184834,   6.36504619,
         6.88748019,   8.58316283,   8.80726429,   9.41456987,
         5.75200431,   5.31224919,   4.86714727,   6.67216624,
         6.29257964,   9.66263912,   7.23633807,   8.19087742,
         6.24055441,   4.25346499,   4.23788714,   5.08272698,
         6.69027125,   6.85190152,   8.42960156,   6.00115302,
         3.24138906,   5.92666335,   6.4679208 ])
In [14]:
primeiro_dia = df_Brasil.observationdate.loc[df_Brasil.confirmed > 0].min()
fig = px.line(x=pd.date_range(primeiro_dia, df_Brasil.observationdate.max())[1:],
        y=tx_dia, title='Taxa de crescimento de casos confirmados no Brasil',
       labels={'y':'Taxa de crescimento', 'x':'Data'})
fig.show()

Predições, presciências ou previsibilidades¶

Vamos construir um modelo de séries temporais para prever os novos casos. Antes analisaremos a série temporal.

Novos casos¶

In [15]:
novoscasos = df_Brasil.casosdia
novoscasos.index = df_Brasil.observationdate

#A função seasonal_decompose usa o método de médias móveis para estimar a tendência.
res = seasonal_decompose(novoscasos)

fig, (ax1,ax2,ax3, ax4) = plt.subplots(4, 1,figsize=(10,8))
ax1.plot(res.observed)
ax2.plot(res.trend)
ax3.plot(res.seasonal)
ax4.scatter(novoscasos.index, res.resid)
ax4.axhline(0, linestyle='dashed', c='black')
plt.show()

Decompondo a série de confirmados¶

Aqui os casos confoirmados foram decompostos em:

Observados (observed) Tendência (trend) Sasonalidade (seasonal) Ruído (resid)

In [16]:
confirmados = df_Brasil.confirmed
confirmados.index = df_Brasil.observationdate

res2 = seasonal_decompose(confirmados)

fig, (ax1,ax2,ax3, ax4) = plt.subplots(4, 1,figsize=(10,8))
ax1.plot(res2.observed)
ax2.plot(res2.trend)
ax3.plot(res2.seasonal)
ax4.scatter(confirmados.index, res2.resid)
ax4.axhline(0, linestyle='dashed', c='black')
plt.show()

Predizendo o número de casos confirmados com um AUTO-ARIMA¶

ARIMA é um modelo auto-regressivo integrado de médias móveis.

Os modelos são ajustados aos dados da série temporal para entender melhor os dados ou para prever pontos futuros na série.

https://pt.wikipedia.org/wiki/ARIMA

In [18]:
from pmdarima.arima import auto_arima

modelo = auto_arima(confirmados)
In [19]:
pd.date_range('2020-05-01', '2020-05-19')
Out[19]:
DatetimeIndex(['2020-05-01', '2020-05-02', '2020-05-03', '2020-05-04',
               '2020-05-05', '2020-05-06', '2020-05-07', '2020-05-08',
               '2020-05-09', '2020-05-10', '2020-05-11', '2020-05-12',
               '2020-05-13', '2020-05-14', '2020-05-15', '2020-05-16',
               '2020-05-17', '2020-05-18', '2020-05-19'],
              dtype='datetime64[ns]', freq='D')
In [31]:
fig = go.Figure(go.Scatter(
    x=confirmados.index, y=confirmados, name='Observado'
))

fig.add_trace(go.Scatter(x=confirmados.index, y = modelo.predict_in_sample(), name='Modelo ML'))
fig.add_trace(go.Scatter(x=pd.date_range('2020-05-20', '2020-05-30'), y=modelo.predict(10), name='Previsão ML'))
fig.update_layout(title='Previsão de casos confirmados para os próximos 10 dias', yaxis_title='Casos confirmados', xaxis_title='Data')
fig.show()
In [32]:
fig = go.Figure(go.Scatter(
    x=confirmados.index, y=confirmados, name='Observado'
))

fig.add_trace(go.Scatter(x=confirmados.index, y = modelo.predict_in_sample(), name='Modelo ML'))
fig.add_trace(go.Scatter(x=pd.date_range('2020-05-20', '2020-06-05'), y=modelo.predict(15), name='Previsão ML'))
fig.update_layout(title='Previsão de casos confirmados para os próximos 15 dias', yaxis_title='Casos confirmados', xaxis_title='Data')
fig.show()
In [34]:
fig = go.Figure(go.Scatter(
    x=confirmados.index, y=confirmados, name='Observado'
))

fig.add_trace(go.Scatter(x=confirmados.index, y = modelo.predict_in_sample(), name='Modelo ML'))
fig.add_trace(go.Scatter(x=pd.date_range('2020-05-20', '2020-06-20'), y=modelo.predict(30), name='Previsão ML'))
fig.update_layout(title='Previsão de casos confirmados para os próximos 30 dias', yaxis_title='Casos confirmados', xaxis_title='Data')
fig.show()

Forecasting com Facebook Prophet¶

O Prophet é um procedimento para prever dados de séries temporais com base em um modelo aditivo em que as tendências não lineares são ajustadas à sazonalidade anual, semanal e diária, além dos efeitos de feriados. Funciona melhor com séries temporais com fortes efeitos sazonais e várias temporadas de dados históricos.

https://github.com/facebook/prophet

In [21]:
!conda install -c conda-forge fbprophet -y
^C
In [ ]:
from fbprophet import Prophet

# preparando os dados
train = confirmados.reset_index()[:-5]
test = confirmados.reset_index()[-5:]

# renomeia colunas
train.rename(columns={"observationdate":"ds","confirmed":"y"},inplace=True)
test.rename(columns={"observationdate":"ds","confirmed":"y"},inplace=True)
test = test.set_index("ds")
test = test['y']

profeta = Prophet(growth="logistic", changepoints=['2020-03-21', '2020-03-30', '2020-04-25', '2020-05-03', '2020-05-10'])

#pop = 1000000
pop = 211463256 #https://www.ibge.gov.br/apps/populacao/projecao/box_popclock.php
train['cap'] = pop

# Treina o modelo
profeta.fit(train)

# Construindo previsões para o futuro
future_dates = profeta.make_future_dataframe(periods=200)
future_dates['cap'] = pop
forecast =  profeta.predict(future_dates)
In [ ]:
fig = go.Figure()

fig.add_trace(go.Scatter(x=forecast.ds, y=forecast.yhat, name='Predição'))
fig.add_trace(go.Scatter(x=test.index, y=test, name='Observados - Teste'))
fig.add_trace(go.Scatter(x=train.ds, y=train.y, name='Observados - Treino'))
fig.update_layout(title='Predições de casos confirmados no Brasil')
fig.show()
In [ ]: